from dataclasses import dataclass
import pandas as pd
from .window import *
from .datagrad import Data2Grade
from collections import defaultdict
import itertools
import math


class CreateFeature:
    
    def __init__(self, applist:[{},{},...], data:pd.DataFrame, cutwin:TimeWindow = TimeWindow([3, 7, 15, 30, 60, 90, 180, 50000])):
        """
        app_list: 用户app原始安装列表数据
        data: 外部数据关联数据表，如竞品列表
        cutwin: TimeWindow类，用于生成时间窗口，拆分原始数据
        """
        self.app_list = pd.DataFrame(applist)
        self.data = data
        self.cutwin = cutwin
        self.get_meta()
        
    def get_meta(self):
        self.meta = {
            'basic_count':'【基础计数】1~2个TAG组合+count',
            'basic_discount':'【类别计数】各TAG的TAG_VALUE的distinctCount',
            'basic_toptag':'【TOP VALUE】各TAG次数TOP的TAG_VALUE取值(类别型)',
            'basic_toptag_count':'【TOP COUNT】各TAG次数最多的次数+count',
            'basic_toptag_ratio':'各TAG次数最多的+count+占比',
            'basic_tag_ratio':'各TAG的count+占比',
            'basic_tag_poly':'1~2个TAG+可聚合实体（金额）+sum/avg/max/min/med',
            'basic_tag_sumratio':'各TAG+可聚合实体+sum+占比',
            'basic_tagcnt_cyclical':'1~2个TAG组合+count+环比变动率',
            'basic_tagsum_cyclical':'各TAG+可聚合实体+sum+环比变动率'
        }
        self.func_map = {
            'basic_count':self.basic_count,
            'basic_discount':self.basic_discount,
            'basic_toptag':self.basic_toptag,
            'basic_toptag_count':self.basic_toptag_count,
            'basic_toptag_ratio':self.basic_toptag_ratio,
            'basic_tag_ratio':self.basic_tag_ratio,
            'basic_tag_poly':self.basic_tag_poly,
            'basic_tag_sumratio':self.basic_tag_sumratio,
            'basic_tagcnt_cyclical':self.basic_tagcnt_cyclical,
            'basic_tagsum_cyclical':self.basic_tagsum_cyclical
        }
        
    
    def __call__(self, tags:list, tags_num:list, fuc_list:list, win = True):
        """组装函数"""
        if win:
            return self.create_winfeatures(func_list=fuc_list,tags=tags,tags_num=tags_num)
        else:
            return self.create_feature(func_list=fuc_list, tags=tags, tags_num=tags_num)
    
    def basic_count(self, data:pd.DataFrame, tags: list, **kwargs) -> dict: 
        """【基础计数】1~2个TAG组合+count"""
        feature_dict = defaultdict(int)
        df = data[tags]
        for i in tags:
            feature_dict[self.join_str([i, 'cnt'])] += df[i].shape[0]
        # # 交叉
        # zu_tags = itertools.combinations(tags, 2)
        # for tag_item in zu_tags:
        #     feature_dict[self.join_str([self.join_str(list(tag_item)), 'cnt'])] += df.groupby(list(tag_item))[tag_item[0]].count()
        return feature_dict
                    
    def basic_discount(self, data:pd.DataFrame, tags: list, **kwargs) -> dict:
        """【类别计数】各TAG的TAG_VALUE的distinctCount"""
        feature_dict = defaultdict(int)
                            
        for i in tags:
            if i not in ['package_name','app_name']:
                feature_dict[self.join_str([i, 'discnt'])] = data[i].nunique()
                for idx,v in data.groupby(i)[i].count().items():
                    feature_dict[self.join_str([i, str(idx), 'discnt'])] = v
            else:
                feature_dict[self.join_str([i, 'discnt'])] = data[i].nunique()
        return feature_dict
    
    def basic_toptag(self, data:pd.DataFrame, tags: list, **kwargs) -> dict: 
        """【TOP VALUE】各TAG次数TOP的TAG_VALUE取值(类别型)"""
        feature_dict = defaultdict(str)
        df = data[tags]
        for i in tags:
            if not df.empty:
                try:
                    feature_dict[self.join_str([i, 'toptag', 'name'])] = df[i].value_counts().idxmax()
                except:
                    continue
        return feature_dict

    def basic_toptag_count(self, data:pd.DataFrame, tags: list, **kwargs) -> dict:
        """【TOP COUNT】各TAG次数最多的次数+count"""
        feature_dict = defaultdict(int)
        df = data[tags]
        for i in tags:
            if not df.empty:
                feature_dict[self.join_str([i, 'topcnt'])] = df[i].value_counts().max()
        return feature_dict

    def basic_toptag_ratio(self, data:pd.DataFrame, tags: list, **kwargs) -> dict:
        """各TAG次数最多的+count+占比"""
        feature_dict = defaultdict(int)
        df = data[tags]
        for i in tags:
            if not df.empty:
                feature_dict[self.join_str([i, 'topratiofp'])] = df[i].value_counts().max()/df[i].shape[0]
        return feature_dict
    
    def basic_tag_ratio(self, data:pd.DataFrame, tags: list, **kwargs) -> dict:
        """各TAG的count+占比"""
        feature_dict = defaultdict(int)
        df = data[tags]
        for i in tags:
            if not df.empty:
                feature_dict[self.join_str([i, 'ratio'])] = df[i].shape[0]/data[i].shape[0]
        return feature_dict
    
    def basic_tag_poly(self, data:pd.DataFrame, tags: list, tags_num: list) -> dict:
        """1~2个TAG+可聚合实体（金额）+sum/avg/max/min/med"""
        bins = 10
        feature_dict = defaultdict(int)
        data[tags_num].fillna(0,inplace=True)
        poly_df = Data2Grade(data[tags_num], bins).data
        for i in tags_num:
            if not data.empty:
                feature_dict[self.join_str([i, 'sum'])] = data[i].sum()
                feature_dict[self.join_str([i, 'max'])] = data[i].max()
                feature_dict[self.join_str([i, 'min'])] = data[i].min()
                feature_dict[self.join_str([i, 'med'])] = data[i].median()
                feature_dict[self.join_str([i, 'avg'])] = data[i].sum()/poly_df[i].shape[0]
        # 交叉 
        ## 聚合型tag交叉
        zu_tags = itertools.combinations(tags_num, 2)
        for tag_item in zu_tags:
            for i, v in poly_df.groupby(f"{tag_item[0]}_{'grade'}")[tag_item[1]].sum().items():
                feature_dict[self.join_str([f"{tag_item[0]}_{'grade'}"+str(i), tag_item[1], 'sum'])] = v
                feature_dict[self.join_str([f"{tag_item[0]}_{'grade'}"+str(i), tag_item[1], 'avg'])] = v/math.ceil(poly_df.shape[0]/bins) if poly_df.shape[0] != 0 else 0
            
            for i, v in poly_df.groupby(f"{tag_item[0]}_{'grade'}")[tag_item[1]].max().items():
                feature_dict[self.join_str([f"{tag_item[0]}_{'grade'}"+str(i), tag_item[1], 'max'])] = v
            
            for i, v in poly_df.groupby(f"{tag_item[0]}_{'grade'}")[tag_item[1]].min().items():
                feature_dict[self.join_str([f"{tag_item[0]}_{'grade'}"+str(i), tag_item[1], 'min'])] = v
            
            for i, v in poly_df.groupby(f"{tag_item[0]}_{'grade'}")[tag_item[1]].median().items():
                feature_dict[self.join_str([f"{tag_item[0]}_{'grade'}"+str(i), tag_item[1], 'med'])] = v
        ## 类别型tag和聚合型tag交叉
        zu_tags = []
        for tag in tags:
            if tag in ['package_name','app_name']:
                continue
            for tn in tags_num:
                zu_tags.append((tag,tn))
        for tag_item in zu_tags:
            for i, v in data.groupby(tag_item[0])[tag_item[1]].sum().items():
                feature_dict[self.join_str([tag_item[0]+str(i), tag_item[1], 'sum'])] = v
                feature_dict[self.join_str([tag_item[0]+str(i), tag_item[1], 'avg'])] = v/math.ceil(data.shape[0]/bins) if data.shape[0] != 0 else 0
            
            for i, v in data.groupby(tag_item[0])[tag_item[1]].max().items():
                feature_dict[self.join_str([tag_item[0]+str(i), tag_item[1], 'max'])] = v
            
            for i, v in data.groupby(tag_item[0])[tag_item[1]].min().items():
                feature_dict[self.join_str([tag_item[0]+str(i), tag_item[1], 'min'])] = v
            
            for i, v in data.groupby(tag_item[0])[tag_item[1]].median().items():
                feature_dict[self.join_str([tag_item[0]+str(i), tag_item[1], 'med'])] = v
            
        return feature_dict
    
    def basic_tag_sumratio(self, data:pd.DataFrame, tags: list, tags_num:list) -> dict:
        """各TAG+可聚合实体+sum+占比"""
        feature_dict = defaultdict(int)
        df = data[tags_num]
        for i in tags_num:
            if not df.empty:
                feature_dict[self.join_str([i, 'sum', 'ratio'])] = df[i].sum()/data[i].sum()
        return feature_dict

    def basic_tagcnt_cyclical(self, data:pd.DataFrame, tags: list, tags_num:list, cycrate=30) -> dict:
        """1~2个TAG组合+count+环比变动率"""
        feature_dict_cyc = defaultdict(int)
        feature_dict = defaultdict(int)
        data1, data2 = self.cutwin.get_cycdata(self.app_list, cycrate)
        df1 = self.deal_data(data1, self.data)
        df2 = self.deal_data(data2, self.data)
        for i in tags:
            if not df1.empty:
                feature_dict[self.join_str([i, 'df1', 'cnt'])] = df1[i].shape[0]
            else:
                feature_dict[self.join_str([i, 'df1', 'cnt'])] = 0
            if not df2.empty:
                feature_dict[self.join_str([i, 'df2', 'cnt'])] = df2[i].shape[0]
            else:
                feature_dict[self.join_str([i, 'df2', 'cnt'])] = 1
            
        for i in tags:
            if feature_dict[self.join_str([i, 'df2', 'cnt'])] == 0:
                feature_dict_cyc[self.join_str([i, 'cycrate'])] = 999999999
            else:
                feature_dict_cyc[self.join_str([i, 'cycrate'])] = feature_dict[self.join_str([i, 'df1', 'cnt'])]/feature_dict[self.join_str([i, 'df2', 'cnt'])]
        return feature_dict_cyc
    
    def basic_tagsum_cyclical(self, data:pd.DataFrame, tags: list, tags_num:list, cycrate=30) -> dict:
        """各TAG+可聚合实体+sum+环比变动率"""
        feature_dict_cyc = defaultdict(int)
        feature_dict = defaultdict(int)
        data1, data2 = self.cutwin.get_cycdata(self.app_list, cycrate)
        df1 = self.deal_data(data1, self.data)
        df2 = self.deal_data(data2, self.data)
        for i in tags_num:
            if not df1.empty:
                feature_dict[self.join_str([i, 'df1', 'cnt'])] = df1[i].sum()
            else:
                feature_dict[self.join_str([i, 'df1', 'cnt'])] = 0
            if not df2.empty:
                feature_dict[self.join_str([i, 'df2', 'cnt'])] = df2[i].sum()
            else:
                feature_dict[self.join_str([i, 'df2', 'cnt'])] = 1
            
        for i in tags_num:
            if feature_dict[self.join_str([i, 'df2', 'cnt'])] == 0:
                feature_dict_cyc[self.join_str([i, 'cycrate'])] = 999999999
            else:
                feature_dict_cyc[self.join_str([i, 'cycrate'])] = feature_dict[self.join_str([i, 'df1', 'cnt'])]/feature_dict[self.join_str([i, 'df2', 'cnt'])]
        return feature_dict_cyc
            
    def deal_data(self, applist, data):
        dfs = defaultdict(pd.DataFrame)
        for idx,row in applist.iterrows():
            dfs[row['package']] = data[data['package_name']==row['package']]
        if not list(dfs.values()):
            return pd.DataFrame
        return pd.concat(list(dfs.values()))
    
    
    def join_str(self,tag_list):
        return '_'.join(tag_list)
    
    def create_feature(self, func_list:list, tags:list, tags_num:list, *args, **kwargs):
        """使用全量数据生成特征函数，不会使用时间窗口对总数据进行拆分"""
        dfs = []
        for func in func_list:
            features = self.func_map[func](data=self.data,tags=tags,tags_num=tags_num)
            dfs.extend(features)
        return self.cutwin.merge_dfs(dfs)
        
    def create_winfeatures(self, func_list:list, tags:list, tags_num:list, *args, **kwargs):
        """使用时间窗口对总数据进行拆分，拆分后的数据生成特征函数"""
        dfs = []
        t_dict = dict(zip(self.cutwin.window_names, self.cutwin.window_days))
        for name, al in self.cutwin.iter(self.app_list):
            df = self.deal_data(al, self.data)
            if not df.empty:
                for func in func_list:
                    if 'cyclical' not in func:
                        features = self.func_map[func](data=df,tags=tags,tags_num=tags_num)
                        if features:
                            features_new = [{f"{key}_{name}":features[key]} for key in features]
                            dfs.extend(features_new)
                    else:
                        features = self.func_map[func](data=df,tags=tags,tags_num=tags_num,cycrate=t_dict[name])
                        if features:
                            features_new = [{f"{key}_{name}":features[key]} for key in features]
                            dfs.extend(features_new)
            
        return self.cutwin.merge_dfs(dfs)
    